# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import inspect
import types
import warnings
import sys
import traceback
from functools import wraps as __wraps
from abc import ABCMeta
from hysop.constants import __DEBUG__, __VERBOSE__, __PROFILE__, HYSOP_ROOT
from hysop.tools.sys_utils import SysUtils
from hysop.tools.warning import HysopDeprecationWarning
[docs]
def wraps(f):
"""
Like functools.wraps but keep trace of the original wrapped function
in the '__wrapped__' attribute. This is usefull to trace functions
calls origin. See hysop.tools.decorators.debug
"""
if __DEBUG__:
if not hasattr(f, "__wrapped__"):
setattr(f, "__wrapped__", f)
wrapper_assignments = functools.WRAPPER_ASSIGNMENTS + ("__wrapped__",)
g = __wraps(f, wrapper_assignments)
else:
g = __wraps(f)
return g
[docs]
def static_vars(**kwargs):
"""
Attach a static variables local to decorated function.
"""
def decorate(f):
for k in kwargs:
setattr(f, k, kwargs[k])
return f
return decorate
[docs]
@static_vars(call_depth=-1)
def debug(f):
"""
Debug decorator
Usage: @debug before function definition you want to debug
If verbose is set to True: prints filename and line of function definition
Else: only print depth of call and function name
"""
verbose = False # __VERBOSE__
if __DEBUG__:
@wraps(f)
def decorator(*args, **kw):
debug.call_depth += 1
cls = None
fw = f
if isinstance(f, types.FunctionType) and hasattr(args[0], "__class__"):
cls = args[0] if isinstance(args[0], type) else type(args[0])
if hasattr(f, "__wrapped__"):
fw = f.__wrapped__
for _cls in inspect.getmro(cls)[::-1]:
if _cls is ABCMeta:
continue
if hasattr(_cls, fw.__name__):
_f = getattr(_cls, fw.__name__)
if hasattr(_f, "__wrapped__"):
_f = _f.__wrapped__
if _f is fw:
cls = _cls
break
cls = cls.__name__
if hasattr(args[0], "name"):
cls += f"({args[0].name})"
print(
"{}{}{}{}{}()".format(
(
"{}:".format(
fw.__code__.co_filename.replace(HYSOP_ROOT, "hysop")
)
if verbose
else ""
),
f"{fw.__code__.co_firstlineno} " if verbose else "",
">" * debug.call_depth if not verbose else "",
f"{cls}::" if (cls is not None) else "",
fw.__name__,
)
)
if "name" in kw:
print("with name {}.".format(kw["name"]))
if f.__name__ == "__new__":
fullclassname = args[0].__mro__[0].__module__ + "."
fullclassname += args[0].__mro__[0].__name__
print(f"=> {fullclassname}")
print()
# Calling f
ret = f(*args, **kw)
# try:
# ret = f(*args, **kw)
# except Exception as e:
# fn='{}{}{}{}()'.format(
# '{}:'.format(f.__code__.co_filename.replace(HYSOP_ROOT, 'hysop')),
# '{}'.format(f.__code__.co_firstlineno),
# '::{}.'.format(cls) if (cls is not None) else '::',
# f.__name__)
# if not hasattr(e, 'debug_error'):
# msg = '\nFATAL ERROR: Failed to call {}:'.format(fn)
# print(msg)
# print('got exception:')
# exc_type, exc_value, exc_traceback = sys.exc_info()
# traceback.print_exception(exc_type, exc_value, exc_traceback, limit=3)
# print
# print('DEBUG CALLSTACK IS:')
# msg = ' >{} with len(*args)={} and **kwds={}.'
# msg=msg.format(fn, len(args), kw.keys())
# print(msg)
# e.debug_error = True
# raise e
debug.call_depth -= 1
if debug.call_depth == -1:
print
return ret
return decorator
else:
# define empty debug decorator:
return f
[docs]
def profile(f):
"""
Decorator to enable function profiling of
a method inside a class. The concerned class
must have a Profiler attribute.
"""
if __PROFILE__:
from hysop.core.mpi import Wtime
@wraps(f)
def _profile(*args, **kwargs):
"""args[0] contains the object"""
t0 = Wtime()
res = f(*args, **kwargs)
args[0]._profiler[f.__name__] += Wtime() - t0
return res
return _profile
else:
return f
[docs]
def deprecated(f):
@wraps(f)
def func(*args, **kwargs):
cls = None
if isinstance(f, types.FunctionType):
if hasattr(args[0], "__class__"):
cls = args[0].__class__.__name__
elif hasattr(args[0], "__name__"):
cls = args[0].__name__
msg = (
"Use of deprecated function {}{}{}{}()".format(
"{}:".format(f.__code__.co_filename.replace(HYSOP_ROOT, "hysop")),
f"{f.__code__.co_firstlineno}",
f"::{cls}." if (cls is not None) else "::",
f.__name__,
),
)
warnings.warn(msg, HysopDeprecationWarning)
return f(*args, **kwargs)
return func
[docs]
def requires_cmd(*args):
"""
Raise a RuntimeError if given executable names are not found
in system PATH.
"""
def decorate(func):
for cmd in args:
if not SysUtils.cmd_exists(cmd):
msg = "Function '{}' requires executable '{}' to be present "
msg += "on your system but it was not found."
msg = msg.format(func.__name__, cmd)
raise RuntimeError(msg)
return func
return decorate
[docs]
def required_property(f):
@wraps(f)
def decorator(*args, **kargs):
msg = "Property corresponding to getter {}() has not been correctly set up in class {}."
msg = msg.format(f.__name__, args[0].__class__)
raise RuntimeError(msg)
return decorator
[docs]
def optional_property(f):
@wraps(f)
def decorator(*args, **kargs):
msg = (
"Property corresponding to getter {}() is optional and "
+ "has not been set up in class {}."
)
msg = msg.format(f.__name__, args[0].__class__)
raise RuntimeError(msg)
return decorator